package com.gameloft.futbol;

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Graphics;
import javax.microedition.midlet.MIDlet;
import com.gameloft.futbol.element.Screen;
import com.gameloft.futbol.element.screen.MainScreen;
import com.gameloft.futbol.element.screen.LoadScreen;
import com.gameloft.input.Keypad;
import com.gameloft.input.Pointer;

/**
 * fCanvas represents the application's Displayable object.
 * This class follows the singleton design pattern, only one instance is allowed.
 *
 * @author demian
 * @version 0.1, 15/09/09
 */
public class fCanvas extends Canvas implements Runnable 
{
	
	/**
	 * A constant holding the desired frame time in milliseconds.
	 */
	public final static int FRAME_TIME = 70;
	
	/**
	 * A constant holding the minimun update time in milliseconds.
	 */
	public final static int MIN_UPDATE_TIME = 10;

	/**
	 * A constant holding the maximun update time in milliseconds.
	 */
	public final static int MAX_UPDATE_TIME = 200;
	
	/**
	 * A constant wich represents the application's exit state.
	 */
	public final static int STATE_EXIT = -1;
	
	/**
	 * A constant wich represents the application's loading state.
	 */
	public final static int STATE_LOAD = 0;

	/**
	 * A constant wich represents the application's main state.
	 */
	public final static int STATE_MAIN = 1;
	
	/**
	 * This variable holds the current application state, the state determines wich
	 * screen element has focus at a given time.
	 */
	static int appState = STATE_MAIN;
	
	/**
	 * The currently focused screen element that renders onto the screen and handles
	 * the input from Keypad and Pointer.	 */
	static Screen screen;
	
	/**
	 * The singleton instance of this class.
	 */
	static fCanvas instance;
	
	/**
	 * Holds the time when previous frame's update was called.
	 */
	static long lastUpdateTime;
	
	
	//Construct the singleton instance on class loading
	static
	{
		instance = new fCanvas();
		instance.setFullScreenMode(true);
		setState(STATE_MAIN);
	}
	
	//Hiden constructor, just one instance allowed.
	private fCanvas()
  {
    super();
		
		try
		{
			//Try to set keycodes by trusting the Canvas-gameAction API
			int[] keyCodes = new int[7];
			keyCodes[0] = getKeyCode(UP);
			keyCodes[1] = getKeyCode(DOWN);
			keyCodes[2] = getKeyCode(LEFT);
			keyCodes[3] = getKeyCode(RIGHT);
			keyCodes[4] = getKeyCode(FIRE);
			keyCodes[5] = getKeyCode(GAME_A);
			keyCodes[6] = getKeyCode(GAME_B);
			Keypad.setCustomKeycodes(keyCodes);
		}
		catch (IllegalArgumentException e)
		{
			//If there was some error then set the most standar keycodes
			Keypad.setDefaultCustomKeycodes();
		}
  }
  
	/**
	 * This implementation of the Runnable interface handles keypad and pointer
   * events at first, then requests a repaint and finally waits until {@link #FRAME_TIME}
	 * milliseconds passes away.
	 */
  public void run()
  {
		while (appState != STATE_EXIT)
    {
      long st = System.currentTimeMillis();
    	
			Keypad.update();
      Pointer.update();
			
			long elapsed = st - lastUpdateTime;
			if (elapsed < MIN_UPDATE_TIME) elapsed = MIN_UPDATE_TIME;
			if (elapsed > MAX_UPDATE_TIME) elapsed = MAX_UPDATE_TIME;
			update(elapsed);
			lastUpdateTime = System.currentTimeMillis();
			
			repaint();
      serviceRepaints();
			
      while ((System.currentTimeMillis() - st) < FRAME_TIME) Thread.yield();
    }
		fMidlet.instance.notifyDestroyed();
  }
	
	/**
	 * Called once per frame to handle the required logic update.
	 * @param elapsed the number of milliseconds that passed since last update.
	 */
	public void update(long elapsed)
  {
		screen.update(elapsed);
		screen.updateChilds(elapsed);
		screen.propagateInvalidation();
  }
  
	/**
	 * Called by the system each time the application request a repaint and renders to the screen.
	 * @param g the Graphics object to be used for rendering the fCanvas.
	 */
	public void paint(Graphics g)
  {
		screen.paint(g);
  }
	
	/**
	 * Changes the application's state.
	 * @param newState an integer representing the desired state
	 */
	public static void setState(int newState)
	{
		switch (newState)
		{
			case STATE_LOAD:
				screen = new LoadScreen();
				break;
			
			case STATE_MAIN:
				screen = new MainScreen();
				break;
		}
	}
	
	/**
	 * Called by the system everytime a key is pressed.
	 * @param keyCode an integer code wich represents a physical key
	 */
	protected void keyPressed(int keyCode)
  {
  	Keypad.keyPressed(keyCode);
  }
  
	/**
	 * Called by the system everytime a key is released.
	 * @param keyCode an integer code wich represents a physical key
	 */
  protected void keyReleased(int keyCode)
  {
  	Keypad.keyReleased(keyCode);
  }
	
	/**
	 * Called by the system everytime the screen is pressed.
	 * @param x,&nbsp;y the coordinates where the screen was pressed
	 */
  protected void pointerPressed(int x, int y)
  {
  	Pointer.pointerPressed(x, y);
  }
  
	/**
	 * Called by the system everytime the screen is dragged.
	 * @param x,&nbsp;y the coordinates where the screen was dragged
	 */
  protected void pointerDragged(int x, int y)
  {
  	Pointer.pointerDragged(x, y);
  }
  
	/**
	 * Called by the system everytime the screen is released.
	 * @param x,&nbsp;y the coordinates where the screen was released
	 */
  protected void pointerReleased(int x, int y)
  {
  	Pointer.pointerReleased(x, y);
  }
	
	/**
	 * Obtains the screen width.
	 * @return the screen width.
	 */
	public static int getScreenWidth()
	{
		return instance.getWidth();
	}
	
	/**
	 * Obtains the screen height.
	 * @return the screen height.
	 */
	public static int getScreenHeight()
	{
		return instance.getHeight();
	}
	
	/**
	 * Tests is the device supports touch screen commands.
	 * @return true if the device supports touch screen commands.
	 */
	public static boolean isTouchScreen()
	{
		return (instance.hasPointerEvents() || instance.hasPointerMotionEvents());
		//return false;
	}
	
}